import java.io.*;
import java.net.*;
import java.util.*;



public class Server extends Thread implements Runnable {
        private ServerSocket serverSocket = null;
        private Hashtable<Socket, DataOutputStream> outputStreams = new Hashtable<Socket, DataOutputStream>();
       
        boolean allowNewPlayers = true,
                        gameStarted = false;
       
        boolean[] playerStatus = null;

        int currentPlayerId = 0,
                        numberOfCurrentPlayers = 0,
                        numberOfAllInPlayers = 0,
                        numberOfPlayersPlayAgain = 0,
                        numberOfPlayersWhoFolded = 0,
                        numberOfPlayers = 0,
                        numberOfChips = 0,
                        dealerButtonHolder = 0,
                        smallBlind = 0,
                        bigBlind = 0,
                        smallBlindHolder = 0,
                        bigBlindHolder = 0,
                        maxBetHolder = 0,
                        movesThisRound = 0,
                        nextPlayerMoveID = 0,
                        gamePot = 0,
                        currentRoundPot = 0,
                        currentRound = 1,
                        currentGame = 1;
       
        int[] betsAllGame,
                        playerChips = null,
                        betsThisRound;
       
        String[] currentDeck = new String[52],
                        putDeck = new String[0],
                        players = new String[0],
                        playerCards = null;
       
        String[][] playerMoves;
       

        /**
         * Constructor;
         * @param port Server port on which server will be listening to players.
         * @param numberOfPlayers Number of players in games.
         * @param numberOfChips Number of chips every player will get on game start.
         * @param smallBlind Value of small blind.
         * @param bigBlind Value of big blind.
         */
        public Server(int port, int numberOfPlayers, int numberOfChips, int smallBlind, int bigBlind) {
               
                try {
                        serverSocket = new ServerSocket(port);
                } catch (Exception e) {
                        System.out.println(" > Cannot connect to port " + port + "!\n   Choose another port.\n");
                        System.exit(-1);
                }
               
                this.numberOfPlayers = numberOfPlayers;
                this.numberOfChips = numberOfChips;
                this.smallBlind = smallBlind;
                this.bigBlind = bigBlind;
               
                players = new String[numberOfPlayers];
                playerCards = new String[numberOfPlayers*4];
                playerChips = new int[numberOfPlayers];
                playerStatus = new boolean[numberOfPlayers];
                playerMoves = new String[numberOfPlayers][2];
                betsThisRound = new int[numberOfPlayers];
                betsAllGame = new int[numberOfPlayers];
                               
                currentDeck = newDeckOfCards();
                currentDeck = shuffleDeck(currentDeck);
               
                handOfCards(numberOfPlayers);
                allocateChips(numberOfPlayers, numberOfChips);
                setPlayersStatus(numberOfPlayers);
               
                try {
                        listen();
                } catch (Exception e) {
                        System.exit(-1);
                }
        }
       
       
        /**
         * Listens if new players want to connect (allowNewPlayers must be set to true).
         */
        void listen() throws IOException {
                new Thread(this).start();
        }
       
       
        /**
         * This is where server listens and check if new players want to connect.
         */
        public void run() {
                System.out.println(" >>> Server is listening...");
               
                try {
                while (allowNewPlayers) {
                        if (numberOfCurrentPlayers == numberOfPlayers) {
                                allowNewPlayers = false;
                                break;                  
                        }
                       
                        Socket playerSocket = serverSocket.accept();
                       
                        currentPlayerId = 0;
                       
                        for (int i = 0; i < numberOfPlayers; i++) {
                                if (playerStatus[i] == false) {
                                        currentPlayerId = i+1;
                                        playerStatus[i] = true;
                                        playerChips[i] = numberOfChips;
                                       
                                        break;
                                }
                        }
                       
                        if (currentPlayerId != 0) {
                                numberOfCurrentPlayers += 1;
                        } else {
                                continue;
                        }
                       
                        System.out.println(" > New player joined the game (ID: " + currentPlayerId + ")");

                        DataOutputStream writer = new DataOutputStream(playerSocket.getOutputStream());
                       
                        outputStreams.put(playerSocket, writer);
                       
                        new ServerThread(this, playerSocket, currentPlayerId);
                       
                       
                        if (numberOfCurrentPlayers == numberOfPlayers && currentGame > 1) {
                                playerChips[currentPlayerId-1] = numberOfChips;
                        }      
                }
                } catch (Exception e) {
                        System.out.println(" > Error while listening :(");
                }

                System.out.println(" > All players connected :)");
        }
       
       
        /**
         * Reset all variables to prepare new game.
         * @return String with ID's of players who can play again separated by space.
         */
        private String prepareNewGame() {
                if (numberOfCurrentPlayers == 0) {
                        return "";
                }
               
                if (numberOfPlayersWhoFolded > 0 && !allowNewPlayers) {
                        allowNewPlayers = true;
                }
               
                String playersCanPlayAgain = "";
                int numberOfPlayersCanPlayAgain = 0;
               
                System.out.println("   Players, who can play 1 more game:");
               
                for (int i = 0; i < numberOfPlayers; i++) {
                        if (players[i] != null && playerChips[i] > bigBlind) {
                                playersCanPlayAgain += " " + (i+1);
                                System.out.print("   " + players[i]);
                                numberOfPlayersCanPlayAgain += 1;
                        } else {
                                playerStatus[i] = false;
                        }
                }
                System.out.println();
               
               
                if (numberOfPlayersCanPlayAgain < numberOfPlayers && !allowNewPlayers) {
                        allowNewPlayers = true;
                }
               
                numberOfCurrentPlayers = 0;
                numberOfAllInPlayers = 0;
                numberOfPlayersWhoFolded = 0;
               
                currentRound = 1;
                currentGame += 1;
               
                movesThisRound = 0;
                currentRoundPot = 0;
                gamePot = 0;
               
                for (int i = 0; i < numberOfPlayers; i++) {
                        if (players[i] != null) {
                                playerStatus[i] = true;
                        }
                }
               
               
                if (gameStarted)
                        return "";

                playerCards = new String[numberOfPlayers*4];
                playerMoves = new String[numberOfPlayers][2];
                betsThisRound = new int[numberOfPlayers];
                betsAllGame = new int[numberOfPlayers];
               
                betsThisRound = new int[numberOfPlayers];
                playerMoves = new String[numberOfPlayers][2];
               
                dealerButtonHolder = 0;
                smallBlindHolder = 0;
                bigBlindHolder = 0;
                maxBetHolder = 0;
               
                currentDeck = newDeckOfCards();
                currentDeck = shuffleDeck(currentDeck);
               
                handOfCards(numberOfPlayers);
                return playersCanPlayAgain;
        }

       
        /**
         * Sends an information that new game is ready, set dealer button and blinds
         * holders.
         */
        synchronized void beginNewGame() {
                gameStarted = true;
                allowNewPlayers = false;
               
                String data;
               
                System.out.println(" > Start of the game (" + currentGame + ").");

               
                setDealerButtonHolder();
                setBlindsHolders();
               
                nextPlayerMoveID = nextPlayer(bigBlindHolder);
               
                data = "newRound 1 ";
                data += nextPlayerMoveID + " ";
                data += players[nextPlayerMoveID-1] + " ";
                data += dealerButtonHolder + " ";
                data += smallBlindHolder + " " + smallBlind + " ";
                data += bigBlindHolder + " " + bigBlind + " ";
                data += possiblePlayerMoves(nextPlayerMoveID);

                sendToAll("startOfTheGame " + currentGame);
                sendToAll("&nbsp; Player <b>" + players[smallBlindHolder-1] + "</b> bet <i>small blind ($" + smallBlind + ")</i>.");
                sendToAll("&nbsp; Player <b>" + players[bigBlindHolder-1] + "</b> bet <i>big blind ($" + bigBlind + ")</i>.");
                sendToAll("tableUpdate potAndMaxBet " + gamePot + " " + bigBlind);
                sendToAll(data);
        }
       
       
        /**
         * Sends a message to all active users.
         */
        void sendToAll(String message) {
                synchronized (outputStreams) {

                        for (Enumeration<DataOutputStream> en = outputStreams.elements(); en.hasMoreElements();) {
                                DataOutputStream writer = (DataOutputStream)en.nextElement();
       
                                try {
                                        writer.writeUTF(message);
                                } catch(IOException e) { }
                        }
                }
        }
       
       
        /**
         * Removes a connection, when 'fold' or connection error.
         * @param playerSocket Player's socket (to find in 'outputStream' and delete).
         * @param playerName
         * @param playerID
         */
        void removeConnection(Socket playerSocket, String playerName, int playerID) {
                if (outputStreams.get(playerSocket) == null)
                        return;
               
                synchronized (outputStreams) {
                        System.out.println(" > Deleting " + playerName + "'s socket.");

                        outputStreams.remove(playerSocket);
                        playerStatus[playerID-1] = false;
                        players[playerID-1] = null;
                       
                        try {
                                playerSocket.close();
                        } catch(IOException e) {
                                System.out.println(" > Error while closing " + playerName + "'s socket.");
                        }
                       
                }
        }

       
        /**
         * Check if end of current round/ game.
         */
        String[] isEndOf(int pID) {
                if (movesThisRound < numberOfCurrentPlayers + numberOfAllInPlayers && currentRound % 2 == 0)
                        return new String[]{"false", "false"};
                else if (movesThisRound < numberOfCurrentPlayers)
                        return new String[]{"false", "false"};
                else {
                        if (currentRound % 2 == 1) {
                                int prevVal = -1;
                               
                                for (int i = 0; i < numberOfPlayers; i++) {
                                        if (playerMoves[i][0] != null && !playerMoves[i][0].equals("null")) {
                                                if (!playerMoves[i][0].equals("allIn") && !playerMoves[i][0].equals("fold")) {
                                                        if (betsThisRound[i] != prevVal && prevVal > -1) {
                                                                return new String[]{"false", "false"};
                                                        } else if (prevVal == -1) {
                                                                prevVal = betsThisRound[i];
                                                        }      
                                                }
                                        }
                                }
                               
                               
                                if (currentRound == 7) {
                                        String allCardsStr = "";
                                       
                                        for (int i = 0; i < numberOfPlayers; i++) {
                                                allCardsStr += " " + players[i];
                                               
                                                String[][] c = sortCards(new String[][]{
                                                                playerCards[4*i].split("-"),
                                                                playerCards[4*i+1].split("-"),
                                                                playerCards[4*i+2].split("-"),
                                                                playerCards[4*i+3].split("-")});
                                               
                                                for (int j = 0; j <= 3; j++)
                                                        allCardsStr += " " + c[j][0] + "-" + c[j][1] + "-" + c[j][2];
                                        }
                                       
                                        sendToAll("tableUpdate showCards" + allCardsStr);
                                        gameStarted = false;
                                       
                                        return new String[]{"true", "true"};
                                } else {
                                        betsThisRound = new int[numberOfPlayers];
                                        playerMoves = new String[numberOfPlayers][2];
                                        nextPlayerMoveID = 0;
                                        movesThisRound = 0;
                                        currentRoundPot = 0;
                                        maxBetHolder = 0;
                                       
                                        currentRound += 1;
                                       
                                        return new String[]{"true", "false", (currentRound-1) + ""};
                                }
                        } else {
                                betsThisRound = new int[numberOfPlayers];
                                playerMoves = new String[numberOfPlayers][2];
                                nextPlayerMoveID = 0;
                                movesThisRound = 0;
                                currentRoundPot = 0;
                                maxBetHolder = 0;
                               
                                currentRound += 1;
                               
                                setDealerButtonHolder();
                                setBlindsHolders();
                               
                                sendToAll("tableUpdate dealerAndBlinds " + dealerButtonHolder + " " + smallBlindHolder + " " + bigBlindHolder);
                               
                                return new String[]{"true", "false", (currentRound-1) + ""};
                        }
                }
        }
       
       
        /**
         * Return possible (next) player moves.
         */
        String possiblePlayerMoves(int pID) {
                if (currentRound == 1) {
                       
                        if (playerChips[pID-1] > 0) {
                                int playerBet = playerChips[pID-1] + betsThisRound[pID-1];
                                int maxBet = maxBetThisRound();
                               
                                if (pID == bigBlindHolder && maxBet <= bigBlind) {
                               
                                        if (playerBet > maxBet)
                                                return "true false false true true";
                                        else
                                                return "true false false false true";
                                } else {

                                        if (playerBet > maxBet && pID != maxBetHolder)
                                                return "false false true true true";
                                        else if (playerBet == maxBet && pID != maxBetHolder)
                                                return "false false true false true";
                                        else
                                                return "false false false false true";
                                }

                        } else {
                                return "false false false false false";
                        }
                       
                } else {
                        if (currentRoundPot == 0) {

                                if (playerChips[pID-1] > 0) {
                                        return "true true false false true";
                                } else {
                                        return "true true false false false";
                                }

                        } else {
                                if (playerChips[pID-1] > 0) {
                                        int playerBet = playerChips[pID-1] + betsThisRound[pID-1];
                                        int maxBet = maxBetThisRound();
                                       
                                       
                                        if (playerBet > maxBet && pID != maxBetHolder)
                                                return "false false true true true";
                                        else if (playerBet == maxBet && pID != maxBetHolder)
                                                return "false false true false true";
                                        else
                                                return "false false false false true";
                                } else {
                                        return "false false false false false";
                                }
                        }
                }
        }
       
       
        /**
         * Checks and remembers player's move. Send information and table updates
         * about registered move.
         * @param playerID ID of player who made a move.
         * @param writer An output stream to write response.
         * @param action Player's move as a String.
         * @param amount Amount (when action was 'bet' or 'raise') or String of ID's of cards
         * which is going to be drew
         * @return false: if player's move was forbidden, true: in other case.
         */
        boolean registerPlayerMove(int playerID, DataOutputStream writer, String action, String amount) throws IOException {
               
                if (!action.equalsIgnoreCase("drawMyCards") && !action.equalsIgnoreCase("dontDrawMyCards")) {
                       
                        int amountInt;

                        try {
                                amountInt = Integer.parseInt(amount);
                        } catch (Exception e) {
                                amountInt = 0;
                        }
                       
                        if (action.equals("check")) {
                                movesThisRound += 1;
                                writer.writeUTF("actionOk");
                                sendToAll("tableUpdate playerBet " + playerID + " " + betsThisRound[playerID-1]);
                                sendToAll("Player <b>" + players[playerID-1] + "</b> played <i>Check</i>.");

                        } else if (action.equals("bet")) {
                                if (amountInt >= playerChips[playerID-1]) {
                                        writer.writeUTF("actionError");
                                        return false;
                                }
                               
                                gamePot += amountInt;
                                betsThisRound[playerID-1] += amountInt;
                                betsAllGame[playerID-1] += amountInt;
                                playerChips[playerID-1] -= amountInt;
                                currentRoundPot += amountInt;
                                maxBetHolder = playerID;
                                movesThisRound += 1;

                                sendToAll("tableUpdate potAndMaxBet " + gamePot + " " + maxBetThisRound());
                                writer.writeUTF("actionOk");
                                writer.writeUTF("yourChips " + playerChips[playerID-1]);
                                writer.writeUTF("tableUpdate yourBet " + betsAllGame[playerID-1]);      
                                sendToAll("tableUpdate playerBet " + playerID + " " + betsThisRound[playerID-1]);
                                sendToAll("Player <b>" + players[playerID-1] + "</b> played <i>Bet ($" + amount + ")</i>.");

                        } else if (action.equals("call") && maxBetHolder != playerID) {
                                int maxBet = maxBetThisRound();
                                int thisMoveBet = maxBet - betsThisRound[playerID-1];
                               
                                if (thisMoveBet >= playerChips[playerID-1]) {
                                        writer.writeUTF("actionError");
                                        return false;
                                }

                                gamePot += thisMoveBet;
                                betsThisRound[playerID-1] = maxBet;
                                betsAllGame[playerID-1] += thisMoveBet;
                                playerChips[playerID-1] -= thisMoveBet;
                                currentRoundPot += thisMoveBet;
                                maxBetHolder = playerID;
                                movesThisRound += 1;

                                sendToAll("tableUpdate potAndMaxBet " + gamePot + " " + maxBetThisRound());
                                writer.writeUTF("actionOk");
                                writer.writeUTF("yourChips " + playerChips[playerID-1]);
                                writer.writeUTF("tableUpdate yourBet " + betsAllGame[playerID-1]);      
                                sendToAll("tableUpdate playerBet " + playerID + " " + betsThisRound[playerID-1]);
                                sendToAll("Player <b>" + players[playerID-1] + "</b> played <i>Call</i>.");

                        } else if (action.equals("raise")) {
                                int maxBet = maxBetThisRound();
                                int thisMoveBet = maxBet + amountInt - betsThisRound[playerID-1];
                               
                                if (thisMoveBet >= playerChips[playerID-1] && (maxBetHolder != playerID || currentRound == 1)) {
                                        writer.writeUTF("actionError");
                                        return false;
                                }

                                gamePot += thisMoveBet;
                                betsThisRound[playerID-1] = maxBet + amountInt;
                                betsAllGame[playerID-1] += thisMoveBet;
                                playerChips[playerID-1] -= thisMoveBet;
                                currentRoundPot += thisMoveBet;
                                maxBetHolder = playerID;
                                movesThisRound += 1;

                                sendToAll("tableUpdate potAndMaxBet " + gamePot + " " + maxBetThisRound());
                                writer.writeUTF("actionOk");
                                writer.writeUTF("yourChips " + playerChips[playerID-1]);
                                writer.writeUTF("tableUpdate yourBet " + betsAllGame[playerID-1]);      
                                sendToAll("tableUpdate playerBet " + playerID + " " + betsThisRound[playerID-1]);
                                sendToAll("Player <b>" + players[playerID-1] + "</b> played <i>Raise ($" + amount + ")</i>.");

                        } else if (action.equals("allIn")) {
                                int allPlayerChips = playerChips[playerID-1];
                               
                                if (allPlayerChips < 1) {
                                        writer.writeUTF("actionError");
                                        return false;
                                }
                               
                                gamePot += allPlayerChips;
                                betsThisRound[playerID-1] += allPlayerChips;
                                betsAllGame[playerID-1] += allPlayerChips;
                                playerChips[playerID-1] = 0;
                                playerStatus[playerID-1] = false;
                                numberOfCurrentPlayers -= 1;
                                numberOfAllInPlayers += 1;

                                sendToAll("tableUpdate potAndMaxBet " + gamePot + " " + maxBetThisRound());
                                writer.writeUTF("actionOk");
                                writer.writeUTF("yourChips 0");
                                writer.writeUTF("tableUpdate yourBet " + betsAllGame[playerID-1]);      
                                sendToAll("tableUpdate playerBet " + playerID + " " + betsThisRound[playerID-1]);
                                sendToAll("tableUpdate allIn " + playerID);
                                sendToAll(" Player <b>" + players[playerID-1] + "</b> played <i>All-in</i>.");

                                if (numberOfCurrentPlayers == 1) {
                                        currentRound = 7;
                                        movesThisRound = 1;
                                }
                        } else if (action.equals("fold")) {
                                playerChips[playerID-1] = 0;
                                playerStatus[playerID-1] = false;
                               
                                String playerMove = players[playerID-1];
                               
                                players[playerID-1] = null;
                                numberOfCurrentPlayers -= 1;
                                numberOfPlayersWhoFolded += 1;
                               
                                writer.writeUTF("disableMoves");
                                sendToAll("Player <b>" + playerMove + "</b> left the game :(");
                                sendToAll("actionOk");
                                sendToAll("tableUpdate fold " + playerID);
                               
                                if (numberOfCurrentPlayers == 1) {
                                        currentRound = 7;
                                        movesThisRound = 1;
                                }
                        } else {
                                movesThisRound += 1;
                        }
                       
                       
                        playerMoves[playerID-1] = new String[]{action, amount};
                        return true;
                } else {
                        if (playerMoves[playerID-1][0] != null) {
                                return false;
                        } else if (action.equalsIgnoreCase("dontDrawMyCards")) {
                                writer.writeUTF("dontDrawCardsSuccess");
                                sendToAll("&nbsp; Player <b>" + players[playerID-1] + "</b> didn't draw any card.");
                        } else {
                                writer.writeUTF("drawCardsSuccess");
                        }
                       
                       
                        playerMoves[playerID-1] = new String[]{action, amount};
                        movesThisRound += 1;
                        return true;
                }
        }
       
       
       
        /**
         * Chooses a winner of current game.
         */
        void chooseWinner() {
                String result = "";
                String[] allHands = new String[numberOfPlayers];
               
                for (int i = 0; i < numberOfPlayers; i++) {
                        String[][] tmp = new String[4][3];
                       
                        if (playerStatus[i] == false && players[i] == null) {
                                allHands[i] = "xxxx";
                               
                        } else {
                                for (int j = 0; j < 4; j++)
                                        tmp[j] = playerCards[i*4+j].split("-");
                       
                                allHands[i] = encodeHand(organizeCards(tmp));
                        }
                       
                        System.out.println(" * " + players[i] + "'s hand code: " + allHands[i]);
                }
               
                String cmp = allHands[0];
                int dupl = 0;
                int winnerID = 1;
               
               
                for (int i = 0; i+1 < allHands.length; i++) {
                        if (cmp.compareTo(allHands[i+1]) == 0) {
                                dupl += 1;
                        } else if (cmp.compareTo(allHands[i+1]) > 0) {
                                cmp = allHands[i+1];
                                winnerID = i+2;
                                dupl = 0;
                        }
                }
               

               
                if (dupl == 0) {
                       
                        if (playerStatus[winnerID-1]) {
                                playerChips[winnerID-1] += gamePot;
                        } else {
                                int awardChips = 0;
                               
                                for (int i = 0; i < numberOfPlayers; i++) {
                                        if (i == winnerID-1)
                                                continue;
                                       
                                        if (playerChips[i] >= betsAllGame[winnerID-1]) {
                                                awardChips += betsAllGame[winnerID-1];
                                                playerChips[i] -= betsAllGame[winnerID-1];
                                        } else {
                                                awardChips += betsAllGame[i];
                                                playerChips[i] = 0;
                                        }
                                }
                               
                                playerChips[winnerID-1] += awardChips + betsAllGame[winnerID-1];
                        }

                        result = "winner " + winnerID + " " + players[winnerID-1] + " " + playerChips[winnerID-1];
                } else {
                        result = "tie";
                }
               
                String playersCanPlayAgain = prepareNewGame();
                sendToAll("endOfGame " + result);
                sendToAll("playersCanPlayAgain" + playersCanPlayAgain);
               
                if (allowNewPlayers) {
                        try {
                                listen();
                        } catch (Exception e) {}
                }
        }
       
       
        /**
         * @return Returns a max bet in current round.
         */
        int maxBetThisRound() {
                try {
                        return betsThisRound[maxBetHolder-1];
                } catch (Exception e) {
                        return 0;
                }
        }
 

        /**
         * Finds an ID of player who is "sitting" on the left of player with input ID.
         * @param ID
         * @return Returns an ID of found player.
         */
        int nextPlayer(int ID) {
                int nextPlayerID = ID % numberOfPlayers + 1;
               
                while (playerStatus[nextPlayerID-1] == false) {
                        nextPlayerID = nextPlayerID % numberOfPlayers + 1;      
                }
               
                nextPlayerMoveID = nextPlayerID;
                return nextPlayerID;
        }
       
       
        /**
         * Sets, who has a dealer button.
         */
        private void setDealerButtonHolder() {
                if (currentRound == 1) {
                        Random rand = new Random();
                        dealerButtonHolder = rand.nextInt(numberOfPlayers) + 1;
                } else {
                        dealerButtonHolder = nextPlayer(dealerButtonHolder);
                }
        }
       

        /**
         * Sets, which players owns small and big blind.
         */
        private void setBlindsHolders() {
                smallBlindHolder = nextPlayer(dealerButtonHolder);
                bigBlindHolder = nextPlayer(smallBlindHolder);
               
                if (currentRound == 1) {
                        playerChips[smallBlindHolder-1] -= smallBlind;
                        playerChips[bigBlindHolder-1] -= bigBlind;

                        gamePot += smallBlind + bigBlind;
                        currentRoundPot += smallBlind + bigBlind;
                       
                        betsThisRound[smallBlindHolder-1] = smallBlind;
                        betsThisRound[bigBlindHolder-1] = bigBlind;
                       
                        betsAllGame[smallBlindHolder-1] = smallBlind;
                        betsAllGame[bigBlindHolder-1] = bigBlind;
                       
                        playerMoves[smallBlindHolder-1] = new String[]{"bet", smallBlind + ""};
                        playerMoves[bigBlindHolder-1] = new String[]{"raise", (bigBlind - smallBlind) + ""};
                       
                        maxBetHolder = bigBlindHolder;

                } else {
                        nextPlayerMoveID = smallBlindHolder;
                }
        }
       
       
        /**
         * Changes an array (output from 'arraySort') to String.
         * @param cardsValues Array of sorted integer values of cards.
         * @return Returns a hand code, for example: "xxkc" for input array
         * [3, 11, 0, 0]
         */
        String encodeHand(int[] cardsValues) {
                String[] chars = new String[]{"x","a","b","c","d","e","f","g","h","i","j","k","l","m"};
                String handCode = "";
               
                for (int i = cardsValues.length-1; i >= 0 ; i--) {
                        handCode += chars[cardsValues[i]];
                }
               
                return handCode;
        }
       
       
        /**
         * Sorts cards.
         * @param unsorted Array of cards in the same format as in method 'organizeCards'.
         * @return Returns sorted array of cards in the same format as input array.
         */
        private String[][] sortCards(String[][] unsorted) {
                String[][] sorted = new String[4][3];
                int j = 0, index = 0;
               
                for (int i = 0; i < unsorted.length; i++) {
                        int min = 15;
                       
                        for (j = 0; j < unsorted.length; j++) {
                                if (unsorted[j][0] != null) {
                                        if (Integer.parseInt(unsorted[j][0]) < min) {
                                                min = Integer.parseInt(unsorted[j][0]);
                                                index = j;
                                        }
                                }
                        }
                       
                        sorted[i] = unsorted[index];
                        unsorted[index] = new String[3];
                }
               
                return sorted;
        }
       
       
        /**
         * Sorts and prepares an array of cards.
         * @param array And array of cards in format: [[VALUE], [COLOR-SHAPE]]
         * (example: [[0] => "13", [1] => "Red-Diamond"])
         */
        int[] organizeCards(String[][] array) {
                String[][] sortedCards = sortCards(array);
                String[][] cards = sortedCards.clone();
                int[][] hands = new int[2][4];


                for (int k = 0; k <= 1; k++) {
                        String values = "";
                        String shapes = "";
                        int[] numeric = new int[4];
                       
                        for (int i = 0; i <= 3; i++) {
                                int min = 100;
                                int minIndex = -1;
                               
                                for (int j = 0; j <= 3; j++) {
                                        if (cards[j][0] == null)
                                                continue;
                                       
                                        if (values.contains("["+cards[j][0]+"]") || shapes.contains(cards[j][2])) {
                                                cards[j] = new String[3];
                                        } else if (Integer.parseInt(cards[j][0]) < min) {
                                                min = Integer.parseInt(cards[j][0]);
                                                minIndex = j;
                                        }
                                }
                               
                                if (minIndex == -1) {
                                        values += "[0]";
                                        numeric[i] = 0;
                                        continue;
                                }
                               
                                values += "["+cards[minIndex][0]+"]";
                                numeric[i] = Integer.parseInt(cards[minIndex][0]);
                                shapes += cards[minIndex][2];
                                cards[minIndex] = new String[3];
                        }
                       
                       
                        for (int l = 0; l <= 3; l++) {
                                cards[l] = sortedCards[3-l];
                        }
                       
                        hands[k] = numeric;
                }
               
                int[] cardsValues = hands[0];
               
                for (int i = 3; i >= 0; i--) {
                        if (hands[0][i] > hands[1][i])
                                cardsValues = hands[1];
                }
               
                return cardsValues;
        }
       
       
        /**
         * Creates a new deck of cards.
         * @return Returns new deck of cards as an array.
         */
        String[] newDeckOfCards() {
                String[] newDeck = new String[52];
               
                int[] numbers = new int[]{1,2,3,4,5,6,7,8,9,10,11,12,13};
                String[] colors = new String[]{"Red-Heart", "Red-Diamond", "Black-Spade", "Black-Club"};
               
                int index = 0;
               
                for (int i = 0; i < numbers.length; i++) {
                        for (int j = 0; j < colors.length; j++) {
                                newDeck[index] = numbers[i] + "-" + colors[j];
                                index += 1;
                        }
                }
               
                return newDeck;
        }
       
       
        /**
         * Shuffles a deck of cards.
         * @param oldDeck A deck of cards (as an array) to shuffle.
         * @return Shuffled deck of cards.
         */
        private String[] shuffleDeck(String[] oldDeck) {
                Random random = new Random();
               
            for (int i = oldDeck.length - 1; i > 0; i--) {
              int index = random.nextInt(i + 1);
             
              String oldIndexValue = oldDeck[index];
             
              oldDeck[index] = oldDeck[i];
              oldDeck[i] = oldIndexValue;
            }
           
            return oldDeck;
        }
       
       
        /**
         * Removes all null values from input array (shorten an array).
         * @param deck An array with null values.
         */
        void sliceAllNulls(String[] deck) {
                int index = 0;
                int newLength = 0;
                String[] newDeck = null;
               
                for (int i = 0; i < deck.length; i++) {
                        if (deck[i] != null)
                                newLength += 1;
                }
               
                newDeck = new String[newLength];
               
                for (int i = 0; i < deck.length; i++) {
                        if (deck[i] != null) {
                                newDeck[index] = deck[i];
                                index += 1;
                        }
                }
               
                currentDeck = new String[newLength];
                currentDeck = newDeck;
        }
       
       
        /**
         * Adds a put card to put deck.
         * @param deck
         * @param card A card to add to put deck.
         */
        private void extendPutDeck(String[] deck, String card) {
                String[] newDeck = new String[deck.length+1];
               
                for (int i = 0; i < deck.length; i++)
                        newDeck[i] = deck[i];
               
                newDeck[deck.length] = card;
               
                putDeck = new String[newDeck.length];
                putDeck = newDeck;
        }
       
       
        /**
         * First hand of cards.
         */
        private void handOfCards(int numberOfPlayers) {
                for (int i = 0; i < numberOfPlayers*4; i++) {
                        playerCards[i] = currentDeck[i];
                        currentDeck[i] = null;
                }
               
                sliceAllNulls(currentDeck);
        }
       
       
        /**
         * Gives every player the same amount of chips.
         */
        void allocateChips(int numberOfPlayers, int numberOfChips) {
                for (int i = 0; i < numberOfPlayers; i++)
                        playerChips[i] = numberOfChips;
        }
       
       
        /**
         * Sets every player status to false (is not active) on the beginning.
         */
        void setPlayersStatus(int numberOfPlayers) {
                for (int i = 0; i < numberOfPlayers; i++)
                        playerStatus[i] = false;
        }
       
       
        /**
         * When a player wants to draw a(/some) card(/s).
         */
        synchronized void drawCards(int playerId, String line) {
                String[] tab = line.split(" ");
               
                for (int i = 1; i < tab.length; i++) {
                        int index = Integer.parseInt(tab[i]) + (playerId-1)*4 - 1;
                       
                        if (currentDeck.length == 0) {
                                currentDeck = shuffleDeck(putDeck);
                                putDeck = new String[0];
                        }
                       
                        // Zapamietaj stara karte
                        // Pobierz nowa karte z nowej talii
                        String oldCard = playerCards[index];
                        String newCard = currentDeck[0];
                       
                        // Usun pobrana karte z talii
                        // Wymien karte na nowa
                        currentDeck[0] = null;
                        playerCards[index] = newCard;
                       
                        // Usun puste miejsce po pobranej karcie z obecnej talii
                        // Dopisz wymieniona karte do odlozonej talii
                        sliceAllNulls(currentDeck);
                        extendPutDeck(putDeck, oldCard);
                }
        }
       
       
      
       
       
        /** Main **/
        static public void main( String args[] ) throws Exception {
       int port, numberOfPlayers, numberOfChips, smallBlind, bigBlind;
       
                port = 4008;
                numberOfPlayers = 4;
                numberOfChips = 1500;
                smallBlind = 3;
                bigBlind = 2 * smallBlind;
      

       new Server(port, numberOfPlayers, numberOfChips, smallBlind, bigBlind);
        }
